#include <stdint.h>
#include <memory.h>
#include <assert.h>
#include <stdio.h>
#include "rabin_chunking.h"
#include "md5.h"

#define SymbolCount 256
#define DigistLength 16
#define SeedLength 64
#define MaxChunkSizeOffset 3
#define MinChunkSizeOffset 2

uint64_t g_gear_matrix[SymbolCount];
uint32_t g_min_fastcdc_chunk_size;
uint32_t g_max_fastcdc_chunk_size;
uint32_t g_expect_fastcdc_chunk_size;

uint64_t g_min_mask;
uint64_t g_max_mask;

enum
{
    Mask_64B,
    Mask_128B,
    Mask_256B,
    Mask_512B,
    Mask_1KB,
    Mask_2KB,
    Mask_4KB,
    Mask_8KB,
    Mask_16KB,
    Mask_32KB,
    Mask_64KB,
    Mask_128KB,

    Mask_256KB,
    Mask_512KB
};

uint64_t g_condition_mask[] = {
    0x00001803110,      // 64B
    0x000018035100,     // 128B
    0x00001800035300,   // 256B
    0x000019000353000,  // 512B
    0x0000590003530000, // 1KB
    0x0000d90003530000, // 2KB
    0x0000d90103530000, // 4KB
    0x0000d90303530000, // 8KB
    0x0000d90313530000, // 16KB
    0x0000d90f03530000, // 32KB
    0x0000d90303537000, // 64KB
    0x0000d90703537000, // 128KB

    0x0000d98703537000, // 256KB
    0x0000d98707537000  // 512KB
};

void fastcdc_init(uint32_t expectCS)
{
    char seed[SeedLength];
    for (int i = 0; i < SymbolCount; i++)
    {
        for (int j = 0; j < SeedLength; j++)
        {
            seed[j] = i;
        }

        g_gear_matrix[i] = 0;
        char md5_result[DigistLength];
        md5_state_t md5_state;
        md5_init(&md5_state);
        md5_append(&md5_state, seed, SeedLength);
        md5_finish(&md5_state, md5_result);

        memcpy(&g_gear_matrix[i], md5_result, sizeof(uint64_t));
    }

    g_min_fastcdc_chunk_size = destor.chunk_min_size;
    g_max_fastcdc_chunk_size = destor.chunk_max_size;
    g_expect_fastcdc_chunk_size = expectCS;
    if (g_expect_fastcdc_chunk_size == 8 * 1024)
    {
        g_min_mask = g_condition_mask[Mask_2KB];
        g_max_mask = g_condition_mask[Mask_32KB];
    }
    else if (g_expect_fastcdc_chunk_size == 4 * 1024)
    {
        g_min_mask = g_condition_mask[Mask_1KB];
        g_max_mask = g_condition_mask[Mask_16KB];
    }
    else if (g_expect_fastcdc_chunk_size == 2 * 1024)
    {
        g_min_mask = g_condition_mask[Mask_512B];
        g_max_mask = g_condition_mask[Mask_8KB];
    }
    else if (g_expect_fastcdc_chunk_size == 16 * 1024)
    {
        g_min_mask = g_condition_mask[Mask_4KB];
        g_max_mask = g_condition_mask[Mask_64KB];
    }
    else if (g_expect_fastcdc_chunk_size == 32 * 1024)
    {
        g_min_mask = g_condition_mask[Mask_8KB];
        g_max_mask = g_condition_mask[Mask_128KB];
    }
    else if (g_expect_fastcdc_chunk_size == 64 * 1024)
    {
        g_min_mask = g_condition_mask[Mask_16KB];
        g_max_mask = g_condition_mask[Mask_256KB];
    }
}

uint64_t fastcdc_chunk_data(unsigned char *p, int n)
{
    uint64_t judge_end = 0;
    uint64_t fingerprint = 0, digest;
    uint64_t i = g_min_fastcdc_chunk_size;
    int Mid = g_min_fastcdc_chunk_size + 8 * 1024;
    // return n;

    if (n <= g_min_fastcdc_chunk_size)
    { // the minimal  subChunk Size.
        // uint64_t pp = (uint64_t)n << 20;
        // printf("%llu %llu\n", pp, pp >> 20);
        return (uint64_t)n;
    }

    // windows_reset();
    if (n > g_max_fastcdc_chunk_size)
        n = g_max_fastcdc_chunk_size;
    else if (n < Mid)
        Mid = n;
    while (i < Mid)
    {
        fingerprint = (fingerprint << 1) + (g_gear_matrix[p[i]]);
        if ((!(fingerprint & g_max_mask)))
        { // AVERAGE*2, *4, *8
            return i;
        }
        i++;
    }
    while (i < n)
    {
        fingerprint = (fingerprint << 1) + (g_gear_matrix[p[i]]);
        if ((!(fingerprint & g_min_mask)))
        { // Average/2, /4, /8
            return i;
        }
        i++;
    }
    // printf("\r\n==chunking FINISH!\r\n");
    return i;

    /*
    assert(p != NULL);
    uint64_t fp = 0;
    if(n < g_max_fastcdc_chunk_size){
        if(n <= g_min_fastcdc_chunk_size){
            return n;
        }else if (n < g_expect_fastcdc_chunk_size){
            for(int i=g_min_fastcdc_chunk_size; i<n; i++){
                fp = ( fp <<1 ) + g_gear_matrix[ p[i] ];
                if(!(fp & g_condition_mask[Mask_64KB])){
                    return i;
                }
            }
            return n;
        }else {
            for(int i=g_min_fastcdc_chunk_size; i<g_expect_fastcdc_chunk_size; i++){
                fp = ( fp <<1 ) + g_gear_matrix[ p[i] ];
                if(!(fp & g_condition_mask[Mask_64KB])){
                    return i;
                }
            }
            for(int i=g_expect_fastcdc_chunk_size; i<n; i++){
                fp = ( fp <<1 ) + g_gear_matrix[ p[i] ];
                if(!(fp & g_condition_mask[Mask_1KB])){
                    return i;
                }
            }
            return n;
        }
    }
    for(int i=g_min_fastcdc_chunk_size; i<g_expect_fastcdc_chunk_size; i++){
        fp = ( fp <<1 ) + g_gear_matrix[ p[i] ];
        if(!(fp & g_condition_mask[Mask_64KB])){
            return i;
        }
    }
    for(int i=g_expect_fastcdc_chunk_size; i<g_max_fastcdc_chunk_size; i++){
        fp = ( fp <<1 ) + g_gear_matrix[ p[i] ];
        if(!(fp & g_condition_mask[Mask_1KB])){
            return i;
        }
    }
    return g_max_fastcdc_chunk_size;
     */
}

#include <stdlib.h>
uint64_t fastcdc_sample_chunk_data(unsigned char *p, int n)
{
    uint64_t judge_end = 0;
    uint64_t fingerprint = 0, digest;
    uint64_t i = g_min_fastcdc_chunk_size;
    int Mid = g_min_fastcdc_chunk_size + 8 * 1024;

    uint32_t* sample = malloc(1250 * sizeof(uint32_t));
    int sample_num = 0;
    // return n;

    if (n <= g_min_fastcdc_chunk_size)
    { // the minimal  subChunk Size.
        // uint64_t pp = (uint64_t)n << 20;
        // printf("%llu %llu\n", pp, pp >> 20);
        free(sample);
        return (uint64_t)n;
    }

    // windows_reset();
    if (n > g_max_fastcdc_chunk_size)
        n = g_max_fastcdc_chunk_size;
    else if (n < Mid)
        Mid = n;
    while (i < Mid)
    {
        fingerprint = (fingerprint << 1) + (g_gear_matrix[p[i]]);
        if ((!(fingerprint & 0x000088035100)) && i > 31)
        { // sample
            sample[sample_num++] = (uint32_t)(fingerprint & 0xffffffff);
        }
        if ((!(fingerprint & g_max_mask)))
        { // AVERAGE*2, *4, *8
            free(sample);
            return i;
        }
        i++;
    }
    while (i < n)
    {
        fingerprint = (fingerprint << 1) + (g_gear_matrix[p[i]]);
        if ((!(fingerprint & 0x000088035100)) && i > 31)
        { // sample
            sample[sample_num++] = (uint32_t)(fingerprint & 0xffffffff);
        }
        if ((!(fingerprint & g_min_mask)))
        { // Average/2, /4, /8
            free(sample);
            return i;
        }
        i++;
    }
    // printf("\r\n==chunking FINISH!\r\n");
    free(sample);
    return i;

    /*
    assert(p != NULL);
    uint64_t fp = 0;
    if(n < g_max_fastcdc_chunk_size){
        if(n <= g_min_fastcdc_chunk_size){
            return n;
        }else if (n < g_expect_fastcdc_chunk_size){
            for(int i=g_min_fastcdc_chunk_size; i<n; i++){
                fp = ( fp <<1 ) + g_gear_matrix[ p[i] ];
                if(!(fp & g_condition_mask[Mask_64KB])){
                    return i;
                }
            }
            return n;
        }else {
            for(int i=g_min_fastcdc_chunk_size; i<g_expect_fastcdc_chunk_size; i++){
                fp = ( fp <<1 ) + g_gear_matrix[ p[i] ];
                if(!(fp & g_condition_mask[Mask_64KB])){
                    return i;
                }
            }
            for(int i=g_expect_fastcdc_chunk_size; i<n; i++){
                fp = ( fp <<1 ) + g_gear_matrix[ p[i] ];
                if(!(fp & g_condition_mask[Mask_1KB])){
                    return i;
                }
            }
            return n;
        }
    }
    for(int i=g_min_fastcdc_chunk_size; i<g_expect_fastcdc_chunk_size; i++){
        fp = ( fp <<1 ) + g_gear_matrix[ p[i] ];
        if(!(fp & g_condition_mask[Mask_64KB])){
            return i;
        }
    }
    for(int i=g_expect_fastcdc_chunk_size; i<g_max_fastcdc_chunk_size; i++){
        fp = ( fp <<1 ) + g_gear_matrix[ p[i] ];
        if(!(fp & g_condition_mask[Mask_1KB])){
            return i;
        }
    }
    return g_max_fastcdc_chunk_size;
     */
}
